/**
 * Baco OPC - Baco Sistemas corp.
 */
package bacoopc;

import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;

/**
 * Classe abstrata que permite que os algoritmos de controle sejam plugáveis.
 * Ela tem dois principais propósitos:
 *  - servir de classe base para os algoritmos de controle;
 *  - e carregar algoritmos de controle a partir de arquivos .class.
 * Uma implementação de um algoritmo de controle deve fazer duas coisas:
 *  - implementar a função de controle f;
 *  - definir um construtor sem argumentos que defina quais são os parâmetros
 *    desse algoritmo.
 *
 * @author Baco Sistemas
 */
public abstract class AlgoritmoDeControle {

    /**
     * Executa o algoritmo de controle, dada a entrada <code>valor</code>.
     */
    public abstract double f(double valor);

    private String variávelDeEntrada;
    private String variávelDeSaída;

    /** Tempo de atualização, em segundos */
    private double t;

    /**
     * Retorna uma instância da função de controle definida pela classe <code>nomeDaClasse</code>,
     * que se encontra no caminho <code>caminho</code>.
     *
     * @param caminho o caminho para a pasta base da classe (conhecido como classpath) ou
     * para o .jar em que a classe se encontra
     * @param nomeDaClasse nome completo da classe. Por exemplo: br.ufrn.pdsw.funcoes.F1
     * @return uma instância da classe encontrada
     * @throws FunçãoDeControle.Exceção por vários motivos
     */
    public static AlgoritmoDeControle carregar(String caminho, String nomeDaClasse)
            throws Excecao {
        try {
            URL[] urls = {new URL(caminho)};
            URLClassLoader carregador = new URLClassLoader(urls, ClassLoader.getSystemClassLoader());

            @SuppressWarnings("unchecked")
            Class classe = carregador.loadClass(nomeDaClasse);

            @SuppressWarnings("unchecked")
            AlgoritmoDeControle f = (AlgoritmoDeControle) classe.getConstructor().newInstance();

            return f;
        } catch (MalformedURLException e) {
            throw new Excecao("URL mal formada: " + e.getMessage());
        } catch (ClassNotFoundException e) {
            throw new Excecao("Classe não encontrada: " + e.getMessage());
        } catch (NoSuchMethodException e) {
            throw new Excecao("O construtor padrão não foi encontrado: " + e.getMessage());
        } catch (InvocationTargetException e) {
            throw new Excecao("Erro na invocação do construtor: " + e.getMessage());
        } catch (IllegalAccessException e) {
            throw new Excecao("Erro de acceso ao construtor: " + e.getMessage());
        } catch (InstantiationException e) {
            throw new Excecao("Erro na instanciação do objeto: " + e.getMessage());
        }
    }
    Map<String, Double> parâmetros = new HashMap<String, Double>();

    // TODO documentação
    public double getParâmetro(String parâmetro) {
        return parâmetros.get(parâmetro);
    }

    // TODO documentação
    public void setParâmetro(String parâmetro, double valor) {
        parâmetros.put(parâmetro, valor);
    }

    public Map<String, Double> getTodosParâmetros() {
        return parâmetros;
    }

    public double getT() {
        return t;
    }

    public void setT(double t) {
        this.t = t;
    }

    /**
     * Exceção ocorrida na função de controle ou no carregamento de uma.
     */
    @SuppressWarnings("serial")
    public static class Excecao extends Exception {

        public Excecao(String msg) {
            super(msg);
        }
    }

    public String getVariávelDeEntrada() {
        return variávelDeEntrada;
    }

    public void setVariávelDeEntrada(String variávelDeEntrada) {
        this.variávelDeEntrada = variávelDeEntrada;
    }

    public String getVariávelDeSaída() {
        return variávelDeSaída;
    }

    public void setVariávelDeSaída(String variávelDeSaída) {
        this.variávelDeSaída = variávelDeSaída;
    }

    /**
     * Um novo algoritmo de controle deve ter um nome para identificá-lo
     *
     * @return o nome do algoritmo de controle
     */
    public abstract String getName() ;

}
